﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.VisualBasic.FileIO;
using System.Xml.Linq;
using System.IO;
using System.Text.RegularExpressions;

/* Universität Trier
 * Fachbereich II - Linguistische Datenverarbeitung
 * Bachelor of Arts
 * Computerlinguistik (HF), English Language and Linguistics (NF)
 * 
 * 
 * A Heuristic Approach to Anaphora Resolution
 * 
 * Bachelor-Arbeit
 * 15. November 2013
 * 
 * Name:			Christopher Michels
 * Matrikelnummer:	1007830
 * 
 * Adresse:			Universitätsring 8d
 * 
 * Telefon:			(06 51) 99 24 11 55
 * E-Mail:			s2chmich@uni-trier.de
 * 
 * Erstprüfer: 		Dr. Sven Naumann
 * Zweitprüfer:		Prof. Dr. Reinhard Köhler
 * 
 * Sources:
 * Biber, Douglas et al. (1999). Longman Grammar of Spoken and Wri􀆩en English. Studies in Language and Linguistics. Harlow, UK: Longman.
 * Mitkov, Ruslan (1998). “Robust pronoun resolution with limited knowledge”. In: Proceedings of the 36th Annual Meeting of the Association for Computational Linguistics and 17th International Conference on Computational Linguistics. Vol. 2. ACL '98. Montreal, Canada: Association for Computational Linguistics, 869–875.
 * Mitkov, Ruslan (2000). “Pronoun Resolution: The Practical Alternative”. In: Corpus-based and Computational Approaches to Discourse Anaphora. Ed. by Simon Botley and Anthony Mark McEnery. Studies in Corpus Linguistics. J. Benjamins Pub. Co.
 * Mitkov, Ruslan (2002). Anaphora resolution. Studies in Language and Linguistics. London, UK: Longman.
 * Santorini, Beatrice (1991). Part-of-Speech Tagging Guidelines for the Penn Treebank Project. URL: http://www.cis.uni-muenchen.de/~schmid/tools/TreeTagger/data/Penn-Treebank-Tagset.pdf (visited on 11/06/2013).
 * Schmid, Helmut (1994). “Probabilistic Part-of-Speech Tagging Using Decision Trees”. In: Proceedings of the International Conference on New Methods in Language Processing. Manchester, UK.
 * Schmid, Helmut (1995). “Improvements In Part-of-Speech Tagging With an Application To German”. In: Proceedings of the ACL SIGDAT-Workshop. Dublin, Ireland.
 * Textalyser (2004). Bernhard Huber Internet Engineering Company. URL: http://textalyser.net (visited on 10/23/2013).
 */

namespace KPAR
{
    /* Static class for static extension methods used
     * in this namespace. 
     */
    public static class XElementExtensions
    {
        /* Extension method used to split the IDs of the object
         * of type XElement (input keyword "this") for which 
         * the method is called. Returns the partial IDs as a
         * string array.
         */
        public static string[] SplitID(this XElement XElem)
        {
            return XElem.Attribute("id").Value.Split(new char[] { ':' });
        }
    }

    /* Main Class of the project KPAR. Manages the main tasks
     * of the anaphora resolution algorithm in its method Main.
     */
    class Program
    {
        #region ImportantLists
        /* Strings of pronouns are stored here. 
         * (lower case) 
         */
        private static List<string> pronounStrings =
            new List<string>(new string[] { "i", "me", "my", "mine", "myself", 
                "you", "your", "yours", "yourself", "he", "him", "his", "himself", 
                "she", "her", "hers", "herself", "it", "its", "itself", 
                "we", "us", "our", "ours", "ourselves", "your", "yours", "yourselves", 
                "they", "them", "their", "theirs", "themselves", 
                "this", "that", "these", "those", 
                "who", "whom", "which", "whose" });

        /* Strings of usually deictic pronouns are stored here. 
         * (lower case) 
         */
        private static List<string> deicticPronounStrings =
            new List<string>(new string[] { "i", "me", "my", "mine", "myself", 
                "you", "your", "yours", "yourself", "we", "us", "our", "ours", 
                "ourselves", "your", "yours", "yourselves" });

        /* POS tags of the different pronoun types are stored here.  
         */
        private static List<string> pronounTypes =
            new List<string>(new string[] { "PP", "PP$", "WP", "WP$", "WDT", "DT" });

        /* Strings of masculine pronouns are stored here. 
         * (lower case) 
         */
        private static List<string> mascPronounStrings =
            new List<string>(new string[] { "he", "him", "himself", "his" });

        /* Strings of feminine pronouns are stored here. 
         * (lower case) 
         */
        private static List<string> femiPronounStrings =
            new List<string>(new string[] { "she", "hers", "herself", "her" });

        /* Strings of neuter pronouns are stored here. 
         * (lower case) 
         */
        private static List<string> neutPronounStrings =
            new List<string>(new string[] { "it", "itself", "its", 
                "this", "that", "which" });

        /* Strings of singular pronouns are stored here. 
         * (lower case) 
         */
        private static List<string> sgPronounStrings =
            new List<string>(new string[] { "i", "me", "my", "mine", "myself", 
                "yourself", "he", "him", "his", "himself", 
                "she", "her", "hers", "herself", "it", "its", "itself",
                "this" });

        /* Strings of plural pronouns are stored here. 
         * (lower case) 
         */
        private static List<string> plPronounStrings =
            new List<string>(new string[] {"we", "us", "our", "ours", "ourselves", 
                "yourselves", "they", "them", "their", "theirs", "themselves", 
                "these", "those" });

        /* Strings of possessive pronouns potentially used
         * attributively are stored here. 
         * (lower case) 
         */
        private static List<string> attrPossStrings =
            new List<string>(new string[] { "my", "your", "his", "her",
                "its", "our", "their", "whose" });

        /* Strings of nouns which allow a number mismatch in
         * anaphora resolution.
         * (experimental)
         * (lower case) 
         */
        private static List<string> numberMismatchNouns =
            new List<string>(new string[] { "government", "team", "parliament", "data" });

        /* Definite articles are stored here.
         * (lower case) 
         */
        private static List<string> defArticles =
            new List<string>(new string[] { "the", "this", "that", "these", "those", 
                "my", "your", "his", "her", "its", "our", "their" });

        /* Indefinite articles are stored here.
         * (lower case) 
         */
        private static List<string> indefArticles =
            new List<string>(new string[] { "a", "an", "other", "another" });

        /* Verbs relevant for the verb preference indicator are stored here.
         * (lower case) 
         */
        private static List<string> preferringVCs =
            new List<string>(new string[] { "analyse", "assess", "check", "consider", "cover", 
                "define", "describe", "develop", "discuss", "examine", 
                "explore", "highlight", "identify", "illustrate", "investigate", 
                "outline", "present", "report", "review", "show", 
                "study", "summarise", "survey", "synthesise" });

        /* Nouns relevant for the noun phrase preference indicator 
         * are stored here.
         */
        private static List<string> preferringNPs =
            new List<string>(new string[] { "chapter", "section", "table" });

        /* Chunk types acting as clause limiters are stored here. 
 */
        private static List<string> clauseLimiterTypes =
            new List<string>(new string[] { "WP$", "WP", "WRB", "WDT", "CC" });

        /* This list stores the noun keywords identified for the current 
         * text. To identify these keywords, the online tool "textalyser"
         * is used.
         */
        private static List<string> domainConcepts =
            new List<string>(new string[] 
		{ 
		"iron", "steam", "water", "cord", "button", 
		"temperature", "fabric", "jet", "feature", "dial", 
		"spray", "selector"
		}); 	
        #endregion

        /* The main tasks of the anaphora resolution algorithm are
         * managed here.
         */
        static void Main(string[] args)
        {
            //Set the static fields of the class Anaphora.
            Anaphora.PronounStrings = pronounStrings;
            Anaphora.DeicticPronounStrings = deicticPronounStrings;

            Anaphora.MascPronounStrings = mascPronounStrings;
            Anaphora.FemiPronounStrings = femiPronounStrings;
            Anaphora.NeutPronounStrings = neutPronounStrings;
            Anaphora.SgPronounStrings = sgPronounStrings;
            Anaphora.PlPronounStrings = plPronounStrings;

            Anaphora.AttrPossStrings = attrPossStrings;
            Anaphora.NumberMismatchNouns = numberMismatchNouns;

            Anaphora.PronounTypes = pronounTypes;
            Anaphora.ClauseLimiterTypes = clauseLimiterTypes;
            Anaphora.PreferringVCs = preferringVCs;
            Anaphora.PreferringNPs = preferringNPs;
            Anaphora.DefArticles = defArticles;
            Anaphora.IndefArticles = indefArticles;
            Anaphora.DomainConcepts = domainConcepts;

            //Variables for the redirected output.
            FileStream outputStream;
            StreamWriter streamWriter;

            //Store the old output destination.
            TextWriter oldTextWriter = Console.Out;

            Console.WriteLine("Welcome to KPAR!");

            string dirName = "";
            string fileNameWithoutEx = "";

            string path = "";
            string[] lineArgs = new string[] { };

            //Handle user input
            while (true)
            {
                Console.WriteLine("------------------\nPlease enter the path of a TreeTagger chunker output file (*.txt):");
                path = Console.ReadLine();
                lineArgs = path.Split(null);

                if (lineArgs.Count() == 1)
                {
                    if (lineArgs[0].ToUpper().EndsWith(".TXT"))
                    {
                        if (File.Exists(lineArgs[0]))
                        {
                            dirName = Path.GetDirectoryName(lineArgs[0]);
                            fileNameWithoutEx = Path.GetFileNameWithoutExtension(lineArgs[0]);
                            break;
                        }
                        else
                        {
                            Console.WriteLine("ERROR: File does not exist.");
                        }
                    }
                    else
                    {
                        Console.WriteLine("ERROR: Invalid file type. A TXT file is required.");
                    }
                }
                else
                {
                    Console.WriteLine("ERROR: Wrong number of arguments.");
                }
            }

            try
            {
                //Set the XML path.
                AccessXML.OutputFilePathXML =
                    dirName + @"\KPAR_" + fileNameWithoutEx + ".xml";

                //Create the XML file.
                AccessXML.CreateXML(path);

                Console.WriteLine("------------------\nSuccessfully created XML file from {0}.", path);
            }
            catch (Exception e)
            {
                Console.WriteLine("Creating XML file from {0} failed.", path);
                Console.WriteLine(e.Message);
                return;
            }

            Console.WriteLine("------------------\nCheck \"KPAR_{0}.xml\" for gender information that needs user\nspecification (gender=\"personal\").", fileNameWithoutEx);
            Console.WriteLine("Press any key to continue.");

            Console.ReadKey();

            string KPARout =
                dirName
                + @"\KPAR_" 
                + fileNameWithoutEx 
                + @"_data.txt";

            if (File.Exists(KPARout))
            {
                File.Delete(KPARout);
            }

            outputStream = new FileStream(KPARout, 
                                            FileMode.OpenOrCreate,
                                            FileAccess.Write);

            streamWriter = new StreamWriter(outputStream);

            //Set the new output writer for the class streamWriter. 
            Console.SetOut(streamWriter);

            //Get the list of pronouns from the text. 
            List<Pronoun> prons = Pronoun.GetPronouns();

            //Try to resolve the coreference links and save the results.

            //NORMAL 
            AccessXML.SaveResults(ResolveAnaphora(prons));

            //BASELINE  AccessXML.SaveResults(ResolveAnaphoraBase(prons));

            string outputXML =
                dirName + @"\KPAR_" + fileNameWithoutEx + ".xml";

            string output_toCheck =
                dirName + @"\KPAR_" + fileNameWithoutEx + "_toCheck.txt";

            AccessXML.CreateTXT(outputXML, output_toCheck);

            //Print the ToString() information for the pronouns and 
            //their candidates and some additional information.

            Console.WriteLine("# Trivial:\t\t" + Pronoun.TrivialIDs.Count);
            Console.WriteLine("# Non-Critical:\t" + Pronoun.NonCriticalIDs.Count);

            for (int i = 1; i <= 80; i++)
                Console.Write("-");
            Console.WriteLine();

            foreach (Pronoun p in prons)
            {
                Console.WriteLine(p.ToString());
                Console.WriteLine("# Candidates:\t" + p.Candidates.Count 
                    + "\t\t\t\t\t\t\t\t\tDEF" + "\tGIV" + "\tDOM" + "\tVCP" + "\tNCP" + "\tLXR" + "\tSHP" + "\tCPP" + "\tRFD" + "\tNPN" + "\tIR" + "\tAPC");
                foreach (Candidate c in p.Candidates)
                {
                    Console.WriteLine(c.ToString());
                }
                for (int i = 1; i <= 80; i++)
                    Console.Write("-");
                Console.WriteLine();
            }

            //Restore the old console ouput destination
            Console.SetOut(oldTextWriter);

            ////Close the temporary output resources.
            streamWriter.Close();
            outputStream.Close();

            //Notify about the completion of the tasks.
            Console.WriteLine("------------------\nAnaphora resolution completed! Press any key to close.");
            Console.ReadKey();
            Console.WriteLine("------------------\n Closing ...");
        }

        /* Takes a list of pronouns and tries to resolve their
         * coreference links. Uses the data of the pronoun and
         * its candidates to determine the candidate indicators
         * and tries to select the best candidate. Returns a list
         * of string pairs (type: string array, length is 2).
         * Index 0 is the pronoun's partial ID, index 1 is the 
         * candidate's partial ID.
         */
        private static List<string[]> ResolveAnaphora(List<Pronoun> pronouns)
        {
            List<string[]> anaphoraPairs = new List<string[]>();

            foreach (Pronoun p in pronouns)
            {
                foreach (Candidate c in p.Candidates)
                {
                    c.DetermineIndicators(p);
                }
                anaphoraPairs.Add(new string[] { p.ID.Last(), Candidate.SelectBestCandidate(p.Candidates) });
            }
            return anaphoraPairs;
        }

        /* Baseline version of ResolveAnaphora
         */
        private static List<string[]> ResolveAnaphoraBase(List<Pronoun> pronouns)
        {
            List<string[]> anaphoraPairs = new List<string[]>();

            foreach (Pronoun p in pronouns)
            {
                foreach (Candidate c in p.Candidates)
                {
                    c.DetermineIndicators(p);
                }
                anaphoraPairs.Add(new string[] { p.ID.Last(), Candidate.SelectBestCandidateBase(p.Candidates) });
            }
            return anaphoraPairs;
        }
    }
}